/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#ifndef simodo_module_variable_Variable
#define simodo_module_variable_Variable

/*! \file Variable.h
    \brief Реализация переменной (именованной) для машины SBL
*/

#include "simodo/ast/Node.h"

#include <memory>
#include <string>
#include <vector>
#include <variant>

#include "simodo/variable/BitFlags.h"

namespace simodo::variable
{
    inline const std::string UNDEF_STRING = "(UNDEF)";
    inline const std::u16string UNDEF_STRING_U16 = u"(UNDEF)";

    class Module_interface;
    class VariableSetWrapper;
    class Variable;
    class Value;

    typedef Value (*ModuleFunction_t) (Module_interface * p_object,
                                        const VariableSetWrapper & args);
                                                    ///< Функция-обработчик вызова метода модуля
    // typedef std::pair<std::shared_ptr<Module_interface>,ModuleFunction_t>
    //                                     ExternalFunction_t;
    // typedef const ast::Node *           InternalFunction_t;
    typedef uint16_t                    index_t;
    typedef std::vector<Variable>       VariableSet_t;

    /*!
     * \brief Перечисление данных (типов значений для переменных и констант, а также функций),
     *        размещаемых в структурах SCI
     */
    enum class ValueType
    {
        Null        = 0,    ///< Значение не определено в результате предыдущих ошибок
        Bool        = 1,    ///< Логический тип
        Int         = 2,    ///< Целочисленный тип
        Float       = 3,    ///< Вещественный тип
        String      = 4,    ///< Строковый тип
        Function    = 5,    ///< Структура с описанием вызова и параметров функции
        Object      = 6,    ///< Набор ассоциативных элементов
        Array       = 7,    ///< Массив
        Ref,                ///< Ссылка на переменную
        // Дополнительные типы для внутреннего использования
        ExtFunction,    ///< Ссылка на внешнюю функцию
        IntFunction,    ///< Ссылка на внутреннюю функцию
    };

    enum class ObjectFlag : uint8_t {
        Fiber = bitShift(0),  ///< Признак объекта, выполняемого удалённо
    };

    using object_flags_t = BitFlags<ObjectFlag>;

    class Object
    {
        VariableSet_t  _variables;
        object_flags_t _flags;

    public:
        Object() = default;
        Object(const VariableSet_t & vars, const object_flags_t & flags)
            : _variables(vars)
            , _flags(flags)
        {}

        Object(const VariableSet_t & vars)
            : Object(vars, object_flags_t::Empty)
        {}

    public:
        const VariableSet_t &   variables() const { return _variables; }
              VariableSet_t &   variables()       { return _variables; }

        const object_flags_t &  flags()     const { return _flags; }
              object_flags_t &  flags()           { return _flags; }

    public:
        bool                    exists(const std::u16string & name) const;
        const Value &           find(const std::u16string & name) const;
              Value &           find(const std::u16string & name);
        Object                  copy() const;

        const Variable &        getVariableByName(const std::u16string & name) const;
        Variable &              getVariableByName_mutable(const std::u16string & name);
        const Variable &        getVariableByIndex(index_t index) const;

        Value                   invoke(const std::u16string & method_name, const VariableSet_t & arguments);
    };

    class VariableRef
    {
        Variable *  _variable_ptr = nullptr;

        mutable std::shared_ptr<Variable> _val_variable;
        Value *     _val_ptr = nullptr;

    public:
        VariableRef() = default;
        explicit VariableRef(Variable & var);
        explicit VariableRef(const Variable & var_c);
        explicit VariableRef(Value & val);
        explicit VariableRef(const Value & val_c);

        const Variable & origin() const;
        Variable & origin();
        void setValue(const Value & value);
    };

    class Array
    {
        ValueType               _common_type    = ValueType::Null; ///< Типа элементов массива
        std::vector<index_t>    _dimensions;                       ///< Размерности массива
        std::vector<Value>      _values;                           ///< Вектор всех значений

    public:
        Array() = default;
        Array(ValueType common_type, const std::vector<index_t> & dimensions, const std::vector<Value> & values)
        : _common_type(common_type), _dimensions(dimensions), _values(values)
        {}
        Array(const std::vector<Value> & values);

        ValueType                   common_type()   const { return _common_type; }
        const std::vector<index_t> &dimensions()    const { return _dimensions; }
        const std::vector<Value> &  values()        const { return _values; }
        std::vector<Value> &        values()              { return _values; }

        Array                       copy()          const;

        const Value &               getValueByIndex(index_t index) const { return getValueByIndex(std::vector<index_t>{index}); }
        const Value &               getValueByIndex(const std::vector<index_t> & index) const;
        VariableRef                 getRefByIndex(const std::vector<index_t> & index) const;
    };

    struct ExternalFunction
    {
        std::shared_ptr<Module_interface>   m;  ///< Модуль
        ModuleFunction_t                    f;  ///< Указатель на функцию модуля
    };

    struct InternalFunction
    {
        const ast::Node *   code = nullptr; ///< Указатель на код функции (корень дерева операционной семантики, т.е. ast::Node)
        Object              closures;       ///< Замыкания для лямбд
        std::shared_ptr<Object> parent {};  ///< Владелец данной функции (необходим для определения self)
    };

    typedef std::variant<
            bool,                       ///< 0 ValueType::Bool
            int64_t,                    ///< 1 ValueType::Int
            double,                     ///< 2 ValueType::Float
            std::u16string,             ///< 3 ValueType::String
            std::shared_ptr<Object>,    ///< 4 ValueType::Object
            std::shared_ptr<Array>,     ///< 5 ValueType::Array
            VariableRef,                ///< 6 ValueType::Reference
            ExternalFunction,           ///< 7 ValueType::ExtFunction
            InternalFunction            ///< 8 ValueType::IntFunction
            >
                        Variant_t;

    class Value 
    {
        ValueType   _type;
        Variant_t   _variant = false;

    protected:
        void setValue(const Value & new_value) 
        { 
            _type = new_value.type(); 
            _variant = new_value.variant(); 
        }

    public:
        Value()                             : _type(ValueType::Null), _variant(int64_t(0)) {}
        Value(ValueType type)               : _type(type) {}
        Value(bool value)                   : _type(ValueType::Bool), _variant(value) {} 
        Value(int64_t value)                : _type(ValueType::Int), _variant(value) {} 
        Value(int value)                    : _type(ValueType::Int), _variant(int64_t(value)) {} 
        Value(double value)                 : _type(ValueType::Float), _variant(value) {} 
        Value(const std::u16string & value) : _type(ValueType::String), _variant(value) {} 
        Value(const char16_t * value)       : _type(ValueType::String), _variant(std::u16string(value)) {} 
        Value(const Object & value)         : _type(ValueType::Object), _variant(std::make_shared<Object>(value)) {} 
        Value(ValueType type, const Object & value) : _type(type), _variant(std::make_shared<Object>(value)) {} 
        Value(std::shared_ptr<Object> value): _type(ValueType::Object), _variant(value) {} 
        Value(ValueType type, std::shared_ptr<Object> value): _type(type), _variant(value) {} 
        Value(const VariableSet_t & variables):_type(ValueType::Object), _variant(std::make_shared<Object>(variables)) {} 
        Value(const Array & value)          : _type(ValueType::Array), _variant(std::make_shared<Array>(value)) {} 
        Value(std::shared_ptr<Array> value) : _type(ValueType::Array), _variant(value) {} 
        Value(const std::vector<Value> & values)    : _type(ValueType::Array), _variant(std::make_shared<Array>(values)) {} 
        Value(const VariableRef & ref)      : _type(ValueType::Ref), _variant(ref) {} 
        Value(const ExternalFunction & value):_type(ValueType::ExtFunction), _variant(value) {} 
        Value(const InternalFunction & value):_type(ValueType::IntFunction), _variant(value) {} 

        Value(const Value & other) = default;
        Value(Value && other) = default;

        Value & operator=(const Value & other) = default;
        Value & operator=(Value && other) = default;

    public:
        ValueType                   type()              const { return _type; }
        const Variant_t &           variant()           const { return _variant; }
        Variant_t &                 variant()                 { return _variant; }

        bool                        getBool()           const { return std::get<bool>(_variant); }
        int64_t                     getInt()            const { return std::get<int64_t>(_variant); }
        double                      getReal()           const { return std::get<double>(_variant); }
        const std::u16string &      getString()         const { return std::get<std::u16string>(_variant); }
        std::shared_ptr<Object>     getObject()         const { return std::get<std::shared_ptr<Object>>(_variant); }
        std::shared_ptr<Object>     getFunction()       const { return std::get<std::shared_ptr<Object>>(_variant); }
        std::shared_ptr<Array>      getArray()          const { return std::get<std::shared_ptr<Array>>(_variant); }
        const VariableRef &         getRef()            const;
        ExternalFunction            getExtFunction()    const { return std::get<ExternalFunction>(_variant); }
        InternalFunction            getIntFunction()    const { return std::get<InternalFunction>(_variant); }
        Object &                    getIntFunctionClosures()  { return std::get<InternalFunction>(_variant).closures; }
        std::shared_ptr<Object> &   getIntFunctionParent()    { return std::get<InternalFunction>(_variant).parent; }

    public:
        Value                       copy()              const;

    public:
        bool                        isUndefined()       const { return _type == ValueType::Null && _variant.index() != 0; }
        bool                        isError()           const { return isUndefined(); }
        bool                        isNull()            const { return _type == ValueType::Null; }
        bool                        isObject()          const { return _type == ValueType::Object; }
        bool                        isArray()           const { return _type == ValueType::Array; }
        bool                        isString()          const { return _type == ValueType::String; }
        bool                        isNumber()          const { return _type == ValueType::Int || _type == ValueType::Float; }
        bool                        isBool()            const { return _type == ValueType::Bool; }
        bool                        isBoolean()         const { return _type == ValueType::Bool; }
        bool                        isFunction()        const { return _type == ValueType::Function; }
        bool                        isRef()             const { return _type == ValueType::Ref; }
    };

    class Specification : public Value
    {
        const std::shared_ptr<Object> getObjectPtr() const;

    public:
        Specification() : Value(ValueType::Null) {}
        Specification(const Object & obj) : Value(obj) {}
        Specification(const VariableSet_t & vars) : Value(vars) {}

        Specification(const Specification & other) = default;
        Specification(Specification && other) = default;

        Specification & operator=(const Specification & other) = default;
        Specification & operator=(Specification && other) = default;

        const Value & find(const std::u16string & spec_name) const;
        void set(const std::u16string & spec_name, const Value & value);

        const std::shared_ptr<Object> object() const
        {
            return getObjectPtr();
        }
        std::shared_ptr<Object> object()
        {
            return const_cast<Specification *>(this)->getObjectPtr();
        }
    };

    class Variable
    {
        std::u16string       _name;              ///< Имя переменной
        Value                _value;             ///< Значение переменной
        inout::TokenLocation _location;          ///< Место объявления переменной в исходном коде
        Specification        _spec;              ///< Спецификаторы переменной (в том числе контракты)

    public:
        Variable() : _location(inout::null_token_location) {}
        Variable(const std::u16string & name, const Value & value, inout::TokenLocation location, const Specification & spec)
            : _name(name), _value(value), _location(location), _spec(spec)
        {}
        Variable(const std::u16string & name, const Value & value, inout::TokenLocation location)
            : _name(name), _value(value), _location(location)
        {}
        Variable(const std::u16string & name, const Value & value)
            : _name(name), _value(value), _location(inout::null_token_location)
        {}
        Variable(const VariableRef & ref, inout::TokenLocation location)
            : _name(ref.origin().name()), _value(ref), _location(location), _spec(ref.origin().spec())
        {}

    public:
        const std::u16string &      name()          const { return _name; }
        const Value &               value()         const { return _value; }
        /// \todo Есть метод setValue! Вообще, нужно выбрать какой-то один стиль для сеттеров!
        Value &                     value()               { return _value; }
        const inout::TokenLocation &location()      const { return _location; }
        const Specification &       spec()          const { return _spec; }
        Specification &             spec()                { return _spec; }
        ValueType                   type()          const { return _value.type(); }
        const Variant_t &           variant()       const { return _value.variant(); }

    public:
        const Variable &            origin()        const;
        Variable &                  origin()        ;

        Variable                    copyVariable()  const;
        VariableRef                 makeReference() const;

        void                        setName(const std::u16string & name)    { _name = name; }
        void                        setValue(Value value)                   { _value = value; }
        void                        setLocation(inout::TokenLocation loc)   { _location = loc; }
    };

    std::string getValueTypeName(ValueType type) noexcept;
    std::u16string toString(const Value & value, bool need_whole_info=true, bool quote_strings=true);

    inline const Variable null_variable(inout::TokenLocation loc=inout::null_token_location, std::u16string name=u"") 
    { 
        return {name, ValueType::Null, loc}; 
    }

    inline const Variable error_variable(inout::TokenLocation loc=inout::null_token_location, std::u16string name=u"") 
    { 
        return {name, {}, loc}; 
    }

    inline const std::u16string SPEC_ORIGIN                 { u"origin" };
    inline const std::u16string SPEC_ORIGIN_CONTRACT        { u"contract" };
    inline const std::u16string SPEC_ORIGIN_STRUCTURE       { u"structure" };
    inline const std::u16string SPEC_ORIGIN_MODULE          { u"module" };
    inline const std::u16string SPEC_ORIGIN_TYPE            { u"type" };
    inline const std::u16string SPEC_ORIGIN_VARIABLE        { u"variable" };
    // inline const std::u16string SPEC_ORIGIN_RETURN_TYPE     { u"return-type" };

    inline const std::u16string SPEC_PROPERTY               { u"property" };
    inline const std::u16string SPEC_BUILTIN                { u"builtin" };
    inline const std::u16string SPEC_REFERENCE              { u"reference" };
    inline const std::u16string SPEC_READONLY               { u"readonly" };
    inline const std::u16string SPEC_PARAMETER              { u"parameter" };
    inline const std::u16string SPEC_INPUT                  { u"input" };
    inline const std::u16string SPEC_OUTPUT                 { u"output" };
    inline const std::u16string SPEC_HIDDEN                 { u"hidden" };
    inline const std::u16string SPEC_FIBER                  { u"fiber" };
    inline const std::u16string SPEC_FLOW                   { u"flow" };
    inline const std::u16string SPEC_TETHERED               { u"tethered" };
    inline const std::u16string SPEC_RECURSIVE              { u"recursive" };
}

#endif // simodo_module_variable_Variable
